package org.abc.fund.service.dataSourceCollection;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.abc.fund.entity.funds.FundHolding;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@Component
public class FundHoldingCrawler {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final Pattern REPORT_DATE_PATTERN =
            Pattern.compile("(20\\d{2})(?:[-年/]?)(\\d{1,2})(?:[-月/]?)(\\d{1,2})?");
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private final HttpClient client = HttpClient.newHttpClient();

    //规范化基金代码，去后缀
    private String normalizeFundCode(String code) {
        if (code == null) return null;
        return code.replace(".OF", "")
                .replace(".XSHE", "")
                .replace(".XSHG", "")
                .replace(".", "")
                .trim();
    }

    //获取基金持仓数据（流程）
    public List<FundHolding> fetchFundHolding(String rawFundCode, int year) {
        List<FundHolding> result = new ArrayList<>();
        String pureCode = normalizeFundCode(rawFundCode);
        if (pureCode == null || pureCode.isEmpty()) {
            log.warn("基金代码为空，跳过");
            return result;
        }
        log.info("抓取基金持仓: 原始代码={} 规范化={} year={}", rawFundCode, pureCode, year);
        String url = String.format(
                "https://fundf10.eastmoney.com/FundArchivesDatas.aspx?type=jjcc&code=%s&topline=50&year=%d",
                pureCode, year);
        try {
            log.info("基金 {} 持仓请求链接：{}", pureCode, url);
            HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .header("User-Agent", "Mozilla/5.0")
                    .header("Referer", "https://fundf10.eastmoney.com/")
                    .build();
            HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
            String body = resp.body();
            if (body == null || body.isBlank()) {
                return result;
            }
            String contentHtml = extractContentFromArchives(body);
            if (contentHtml == null || contentHtml.isBlank()) {
                log.info("基金 {} 持仓接口未解析出表格内容，可能暂无披露", pureCode);
                return result;
            }
            result = parseHoldingsFromHtml(contentHtml, pureCode, year);
            log.info("基金 {} 持仓解析完成，共 {} 条", pureCode, result.size());
        } catch (Exception e) {
            log.error("基金 {} 持仓抓取失败：{}", pureCode, e.getMessage(), e);
        }
        return result;
    }

    //内容提取
    private String extractContentFromArchives(String body) {
        if (body == null) {
            return null;
        }
        Matcher m = Pattern.compile("content:\"([\\s\\S]*?)\"").matcher(body);
        if (m.find()) {
            String content = m.group(1);
            content = content.replace("\\n", "")
                    .replace("\\r", "")
                    .replace("\\t", "")
                    .replace("\\\"", "\"");
            return content;
        }
        int idx = body.indexOf("<table");
        if (idx >= 0) {
            return body.substring(idx);
        }
        return null;
    }

   //HTML解析
    private List<FundHolding> parseHoldingsFromHtml(String contentHtml, String fundCode, int defaultYear) {
        if (contentHtml == null || contentHtml.isBlank()) {
            return List.of();
        }
        Document doc = Jsoup.parse(contentHtml);
        List<FundHolding> list = new ArrayList<>();
        Elements tables = doc.select("table");
        for (Element table : tables) {
            String reportLabel = resolveReportLabel(table, defaultYear);
            LocalDate reportDate = parseReportDate(reportLabel, defaultYear);

            ColumnIndexes indexes = resolveColumnIndexes(table);
            if (!indexes.isValid()) {
                log.debug("基金 {} 表头无法解析需要的列，跳过", fundCode);
                continue;
            }
            Elements rows = table.select("tbody > tr");
            for (Element row : rows) {
                Elements cells = row.select("td");
                if (cells.size() <= Math.max(Math.max(indexes.stockCodeIdx(), indexes.stockNameIdx()),
                        Math.max(indexes.ratioIdx(), indexes.marketValueIdx()))) {
                    continue;
                }
                String stockCode = safeCellText(cells, indexes.stockCodeIdx());
                String stockName = safeCellText(cells, indexes.stockNameIdx());
                if (stockName.isEmpty()) {
                    continue;
                }
                String ratioText = safeCellText(cells, indexes.ratioIdx());
                String mvText = safeCellText(cells, indexes.marketValueIdx());
                String rowReportText = indexes.reportIdx() >= 0
                        ? safeCellText(cells, indexes.reportIdx())
                        : reportLabel;
                LocalDate rowReportDate = indexes.reportIdx() >= 0
                        ? parseReportDate(rowReportText, defaultYear)
                        : reportDate;
                FundHolding holding = new FundHolding();
                holding.setFundCode(fundCode);
                holding.setStockCode(stockCode);
                holding.setStockName(stockName);
                holding.setHoldingRatio(parsePercent(ratioText));
                holding.setProportion(holding.getHoldingRatio());
                holding.setMarketValue(parseAmount(mvText));
                String trimmedReport = rowReportText;
                if (trimmedReport != null && trimmedReport.length() > 50) {
                    trimmedReport = trimmedReport.substring(0, 50);
                }
                holding.setReportDate(trimmedReport);
                holding.setReportDateLocalDate(rowReportDate);
                holding.setPeriodEndDate(rowReportDate);
                list.add(holding);
            }
        }
        return list;
    }

    //表头解析
    private ColumnIndexes resolveColumnIndexes(Element table) {
        Elements headers = table.select("thead th");
        int codeIdx = -1;
        int nameIdx = -1;
        int ratioIdx = -1;
        int marketValueIdx = -1;
        int reportIdx = -1;
        for (int i = 0; i < headers.size(); i++) {
            String text = headers.get(i).text();
            if (text.contains("股票代码")) {
                codeIdx = i;
            } else if (text.contains("股票名称")) {
                nameIdx = i;
            } else if (text.contains("占净值比例")) {
                ratioIdx = i;
            } else if (text.contains("持仓市值")) {
                marketValueIdx = i;
            } else if (text.contains("报告期")) {
                reportIdx = i;
            }
        }
        return new ColumnIndexes(codeIdx, nameIdx, marketValueIdx, ratioIdx, reportIdx);
    }

    //标签解析
    private String resolveReportLabel(Element table, int defaultYear) {
        Element cursor = table.previousElementSibling();
        while (cursor != null) {
            String text = cursor.text();
            if (text != null && !text.isBlank()) {
                return text.trim();
            }
            cursor = cursor.previousElementSibling();
        }
        return defaultYear + "-12-31";
    }

    //日期解析
    private LocalDate parseReportDate(String reportLabel, int defaultYear) {
        if (reportLabel == null || reportLabel.isBlank()) {
            return LocalDate.of(defaultYear, 12, 31);
        }
        Matcher matcher = REPORT_DATE_PATTERN.matcher(reportLabel);
        if (matcher.find()) {
            String year = matcher.group(1);
            String month = matcher.group(2);
            String day = matcher.group(3) != null ? matcher.group(3) : "1";
            String normalized = String.format("%s-%02d-%02d",
                    year,
                    Integer.parseInt(month),
                    Integer.parseInt(day));
            try {
                return LocalDate.parse(normalized, DATE_FORMATTER);
            } catch (DateTimeParseException ignored) {
            }
        }
        return LocalDate.of(defaultYear, 12, 31);
    }

    //百分比解析
    private BigDecimal parsePercent(String s) {
        if (s == null || s.isBlank()) {
            return null;
        }
        String normalized = s.replace("%", "").trim();
        try {
            BigDecimal percent = new BigDecimal(normalized);
            return percent.divide(BigDecimal.valueOf(100), 8, RoundingMode.HALF_UP);
        } catch (NumberFormatException e) {
            log.warn("百分比解析失败: {}", s);
            return null;
        }
    }

    //金额解析
    private BigDecimal parseAmount(String s) {
        if (s == null || s.isBlank()) {
            return null;
        }
        String normalized = s.replace(",", "").trim();
        try {
            return new BigDecimal(normalized);
        } catch (NumberFormatException e) {
            log.warn("金额解析失败: {}", s);
            return null;
        }
    }

    //列索引解析
    private record ColumnIndexes(int stockCodeIdx, int stockNameIdx, int marketValueIdx, int ratioIdx, int reportIdx) {
        boolean isValid() {
            return stockCodeIdx >= 0 && stockNameIdx >= 0 && ratioIdx >= 0;
        }
    }

    //安全获取单元格文本
    private String safeCellText(Elements cells, int idx) {
        if (idx < 0 || idx >= cells.size()) {
            return "";
        }
        return cells.get(idx).text().trim();
    }
}
